#include "Date.hpp"
#include <algorithm>

// constructor taking Size as days
// the argument must be within (0, 2^32)
Date::Date(Size days)
{
    // calculate the year
    Size y400 = days / YtoD_400;
    Size y100 = (days - y400 * YtoD_400) / YtoD_100;
    Size y4 = (days - y400 * YtoD_400 - y100 * YtoD_100) / YtoD_4;
    Size y = (days - y400 * YtoD_400 - y100 * YtoD_100 - y4 * YtoD_4) / 365;
    Size d = days - y400 * YtoD_400 - y100 * YtoD_100 - y4 * YtoD_4 - y * 365;
    this->year = y400 * 400 + y100 * 100 + y4 * 4 + y;

    // check if leap and choose the months vector accordingly
    std::vector<Size> currYear = isLeapYear(this->year) ? monthsVec_l : monthsVec_n;

    // calculate day and month using find_if + lambda
    Size D_accumu = 0, M_accumu = 0;
    // @bug    fixed:  the variabbles above hade been declared inside the find_if as static
    //                 which caused the bug. It works fine now after being move outside.

    auto temp = std::find_if(currYear.cbegin(), currYear.cend(), [&](Size m) {
        D_accumu += m;
        M_accumu++;

        if (d < D_accumu)
        {
            this->month = M_accumu;
            this->day = d + m - D_accumu;

            return true;
        }
        else
        {
            return false;
        }
    });
}

// construcotr taking iostream
Date::Date(std::istream &is, std::ostream &os)
{
    is >> day >> month >> year;

    if (is)
    {
        if (check(*this))
            return;
        else
        {
            os << "Invalid input! Object is default initialized.";
            *this = Date();
        }
    }
    else
    {
        os << "Invalid input! Object is default initialized.";
        *this = Date();
    }
}

// copy constructor
Date::Date(const Date &d) : day(d.day), month(d.month), year(d.year)
{
}

// move constructor
Date::Date(Date &&d) NOEXCEPT : day(d.day), month(d.month), year(d.year)
{
    std::cout << "copy moving";
}

// copy operator=
Date &Date::operator=(const Date &d)
{
    this->day = d.day;
    this->month = d.month;
    this->year = d.year;

    return *this;
}

// move operator=
Date &Date::operator=(Date &&rhs) NOEXCEPT
{
    if (this != &rhs)
    {
        this->day = rhs.day;
        this->month = rhs.month;
        this->year = rhs.year;
    }
    std::cout << "moving" << std::endl;

    return *this;
}

// conver to days
Date::Size Date::toDays() const
{
    Size result = this->day;

    // check if leap and choose the months vector accordingly
    std::vector<Size> currYear = isLeapYear(this->year) ? monthsVec_l : monthsVec_n;

    // calculate result + days by months
    for (auto it = currYear.cbegin(); it != currYear.cbegin() + this->month - 1; ++it)
        result += *it;

    // calculate result + days by years
    result += (this->year / 400) * YtoD_400;
    result += (this->year % 400 / 100) * YtoD_100;
    result += (this->year % 100 / 4) * YtoD_4;
    result += (this->year % 4) * YtoD_1;

    return result;
}

// member operators:   +=  -=

Date &Date::operator+=(Date::Size offset)
{
    *this = Date(this->toDays() + offset);
    return *this;
}

Date &Date::operator-=(Date::Size offset)
{
    if (this->toDays() > offset)
        *this = Date(this->toDays() - offset);
    else
        *this = Date();

    return *this;
}

// non-member operators:  <<  >>  -   ==  !=  <   <=  >   >=

std::ostream &operator<<(std::ostream &os, const Date &d)
{
    os << d.day << " " << d.month << " " << d.year;
    return os;
}

std::istream &operator>>(std::istream &is, Date &d)
{
    if (is)
    {
        Date input = Date(is, std::cout);
        if (check(input))
            d = input;
    }
    return is;
}

int operator-(const Date &lhs, const Date &rhs)
{
    return lhs.toDays() - rhs.toDays();
}

bool operator==(const Date &lhs, const Date &rhs)
{
    return (lhs.day == rhs.day) && (lhs.month == rhs.month) && (lhs.year == rhs.year);
}

bool operator!=(const Date &lhs, const Date &rhs)
{
    return !(lhs == rhs);
}

bool operator<(const Date &lhs, const Date &rhs)
{
    return lhs.toDays() < rhs.toDays();
}

bool operator<=(const Date &lhs, const Date &rhs)
{
    return (lhs < rhs) || (lhs == rhs);
}

bool operator>(const Date &lhs, const Date &rhs)
{
    return !(lhs <= rhs);
}

bool operator>=(const Date &lhs, const Date &rhs)
{
    return !(lhs < rhs);
}

Date operator-(const Date &lhs, Date::Size rhs)
{ // ------------------------------------- ^^^ rhs must not be larger than 2^32-1
    // copy lhs
    Date result(lhs);
    result -= rhs;

    return result;
}

Date operator+(const Date &lhs, Date::Size rhs)
{ // ------------------------------------- ^^^ rhs must not be larger than 2^32-1
    // copy lhs
    Date result(lhs);
    result += rhs;

    return result;
}
